#include "icmp.h"

#include "ip.h"
#include "net.h"

/**
 * @brief 发送icmp响应
 *
 * @param req_buf 收到的icmp请求包
 * @param src_ip 源ip地址
 */
static void icmp_resp(buf_t *req_buf, uint8_t *src_ip) {
    // TO-DO
    // Step1 初始化并封装封装 ICMP 报头和数据
    buf_init(&txbuf, req_buf->len);

    // 只需对type，checksum进行修改
    icmp_hdr_t *header = (icmp_hdr_t *)txbuf.data;
    header->type = ICMP_TYPE_ECHO_REPLY;
    header->checksum16 = 0;
    header->checksum16 = checksum16((uint16_t *)header, txbuf.len);  // 注意 ICMP 校验和涵盖整个报文

    // 数据部分直接拷贝来自接收的回显请求报文中的数据
    memcpy(txbuf.data, req_buf->data, req_buf->len);

    // Step2 通过 IP 协议发送数据报：
    ip_out(&txbuf, src_ip, NET_PROTOCOL_ICMP);
}

/**
 * @brief 处理一个收到的数据包
 *
 * @param buf 要处理的数据包
 * @param src_ip 源ip地址
 */
void icmp_in(buf_t *buf, uint8_t *src_ip) {
    // TO-DO
    // Step1 报头长度检测：
    if (buf->len < sizeof(icmp_hdr_t)) {
        return;
    }

    // Step2 查看 ICMP 类型：重点关注是否为回显请求类型。
    icmp_hdr_t *header = (icmp_hdr_t *)buf->data;
    uint8_t type = header->type;

    // Step3 ICMP 类型是回显请求，回送一个回显应答（ping 应答）。
    if (type == ICMP_TYPE_ECHO_REQUEST) {
        icmp_resp(buf, src_ip);
    }
}

/**
 * @brief 发送icmp不可达
 *  本实验仅要求输出 icmp unreachable 的差错报文，且仅支持如下差错代码：
 *      ICMP_CODE_PROTOCOL_UNREACH, 协议不可达
 *      ICMP_CODE_PORT_UNREACH = 3, 端口不可达
 *
 * @param recv_buf 收到的ip数据包
 * @param src_ip 源ip地址
 * @param code icmp code，协议不可达或端口不可达
 */
void icmp_unreachable(buf_t *recv_buf, uint8_t *src_ip, icmp_code_t code) {
    // TO-DO
    // Step1 初始化并填写 ICMP 报头首部
    buf_init(&txbuf, sizeof(icmp_hdr_t) + sizeof(ip_hdr_t) + 8);
    icmp_hdr_t *header = (icmp_hdr_t *)txbuf.data;
    header->type = ICMP_TYPE_UNREACH;
    header->code = code;

    // Step2 填写数据：
    // 数据部分包含 IP 数据报首部和 IP 数据报的前 8 个字节的数据字段
    memcpy(txbuf.data + sizeof(icmp_hdr_t), recv_buf->data, sizeof(ip_hdr_t) + 8);

    // Step3 计算校验和
    header->checksum16 = 0;
    header->checksum16 = checksum16((uint16_t *)txbuf.data, txbuf.len);

    // Step4 发送数据报：将封装好的 ICMP 差错数据报发送出去。
    ip_out(&txbuf, src_ip, NET_PROTOCOL_ICMP);
}

/**
 * @brief 初始化icmp协议
 *
 */
void icmp_init() {
    net_add_protocol(NET_PROTOCOL_ICMP, icmp_in);
}